/**
 * Note git repository
 * @author Wangtd
 */
const fs = require('fs');
const path = require('path');
const git = require("nodegit");

const config = require('../config.js');
const util = require('./util.js');

const gitRepository = util.path(config.noteDir, '.git');

var commitQueues = [];
var doCommitQueues = function() {
    if (commitQueues.length > 0) {
        var _commitQueue = commitQueues.shift();
        git.Repository.open(gitRepository).then(function(repo) {
            _commitQueue(repo).catch(function(e) {
                console.error(e);
            }).done(function() {
                doCommitQueues();
            });
        });
    } else {
        setTimeout(doCommitQueues, 10);
    }
};
//doCommitQueues();

function gitPath(file) {
    return path.relative(config.noteDir, file).replace(/\\/g, '/');
}

function gitSignature(email) {
    var _name = email;
    if (email.indexOf('@') != -1) {
        _name = email.substring(0, email.indexOf('@'));
    }
    return git.Signature.now(_name, email);
}

function hasHistoryFile(file, delta) {
    return (delta.oldFile() && delta.oldFile().path() == file)
            || (delta.newFile() && delta.newFile().path() == file);
}

module.exports = {
    initRepository: function() {
        if (!fs.existsSync(gitRepository)) {
            // git init
            git.Repository.init(config.noteDir, 0).then(function(repo) {
                var _index;
                repo.refreshIndex().then(function(index) {
                    _index = index;
                    // add .gitkeep file
                    var gitkeep = util.path(config.noteDir, '.gitkeep');
                    util.writeFileSync(gitkeep, '# !.gitkeep', true);
                    // add all paths
                    return _index.addAll();
                }).then(function() {
                    return _index.write();
                }).then(function() {
                    return _index.writeTree();
                }).then(function(oid) {
                    var _author = _committer = gitSignature('admin@example.com');
                    // git commit
                    return repo.createCommit("HEAD", _author, _committer, 'Initial commit', oid, []);
                }).done();
            });
        }
    },
    startCommitTask: function() {
        doCommitQueues();
    },
    commitFiles: function(fnFileHandler, email, message) {
        commitQueues.push(function(repo) {
            if (typeof(fnFileHandler) === 'function') {
                fnFileHandler();
            }
            //
            return repo.getStatus().then(function(statuses) {
                if (statuses && statuses.length > 0) {
                    var _index, _oid;
                    //
                    return repo.refreshIndex().then(function(index) {
                        _index = index;
                        // add all paths
                        return _index.addAll();
                    }).then(function() {
                        return _index.write();
                    }).then(function() {
                        return _index.writeTree();
                    }).then(function(oid) {
                        _oid = oid;
                        return git.Reference.nameToId(repo, "HEAD");
                    }).then(function(head) {
                        return repo.getCommit(head);
                    }).then(function(parent) {
                        var _author = _committer = gitSignature(email);
                        // git commit
                        return repo.createCommit("HEAD", _author, _committer, message, _oid, [parent]);
                    });
                }
            });
        });
    },
    revertFile: function(file, sha, email, message) {
        var _this = this;
        return _this.readFile(file, sha).then(function(data) {
            // git commit
            _this.commitFiles(function() {
                util.writeFileSync(file, data, true);
            }, email, message);
        });
    },
    readFile: function(file, sha) {
        return git.Repository.open(gitRepository).then(function(repo) {
            return repo.getCommit(sha);
        }).then(function(commit) {
            return commit.getEntry(gitPath(file))
                .catch(function(e) {
                    // TODO
                });
        }).then(function(entry) {
            if (entry) {
                return entry.getBlob().then(function(blob) {
                    return blob.toString();
                }).catch(function(e) {
                    return e;
                });
            } else {
                return '';
            }
        });
    },
    diffCommits: function(file, sha, shb) {
        return git.Repository.open(gitRepository).then(function(repo) {
            return Promise.all([repo.getCommit(sha), repo.getCommit(shb)]).then(function(commits) {
                return Promise.all(util.map(commits, function(commit) {
                    return commit.getTree();
                }));
            }).then(function(trees) {
                return trees[0].diff(trees[1]);
            }).then(function(diff) {
                return diff.patches();
            }).then(function(patches) {
                var _file = gitPath(file);
                var _patch = util.find(patches, function(patch) {
                    return hasHistoryFile(_file, patch);
                });
                if (_patch) {
                    return _patch.hunks().then(function(hunks) {
                        var promises = util.map(hunks, function(hunk) {
                            return hunk.lines().then(function(lines) {
                                var _lines = [];
                                // header
                                _lines.push({
                                    origin: '@',
                                    content: hunk.header().trim(),
                                    newLineno: '---',
                                    oldLineno: '---'
                                });
                                // lines
                                util.each(lines, function(line) {
                                    _lines.push({
                                        origin: String.fromCharCode(line.origin()),
                                        content: line.content().replace(/(^[\r\n]+)|([\r\n]+$)/g, ''),
                                        newLineno: line.newLineno(),
                                        oldLineno: line.oldLineno()
                                    });
                                });
                                return _lines;
                            });
                        });
                        return Promise.all(promises).then(function(diffLines) {
                            var _diffLines = [];
                            util.each(diffLines, function(lines) {
                                util.each(lines, function(line) {
                                    _diffLines.push(line);
                                });
                            });
                            return _diffLines;
                        });
                    });
                } else {
                    return [];
                }
            });
        });
    },
    walkHistory: function(size, options) {
        return git.Repository.open(gitRepository).then(function(repo) {
            var _parent = options.parent;
            var _file = options.file;
            var _sha = options.sha;
            var _size = size || 100;
            var _shaCommit = (_sha ? repo.getCommit(_sha) : repo.getMasterCommit());
            return _shaCommit.then(function(commit) {
                var _commits = [];
                // walk commit history
                var _walker = repo.createRevWalk();
                var _ending = false;
                _walker.push(commit.sha());
                _walker.sorting(git.Revwalk.SORT.TIME);
                function _walkHistory() {
                    if (_commits.length >= _size || _ending) {
                        return { commits: _commits, ending: _ending };
                    }
                    return _walker.next().then(function(oid) {
                        return repo.getCommit(oid).then(function(_commit) {
                            _ending = _commit.parentcount() == 0;
                            if (_commit.sha() != _sha) {
                                return _commit.getDiff().then(function(diffs) {
                                    var files = [];
                                    for (var i = 0; i < diffs.length; i++) {
                                        for (var j = 0; j < diffs[i].numDeltas(); j++) {
                                            var delta = diffs[i].getDelta(j);
                                            var status = delta.status();
                                            var diffFile = (status == 1 ? delta.newFile() : delta.oldFile());
                                            var path = diffFile.path();
                                            if ((!_parent || path.indexOf(_parent) == 0)
                                                    && (!_file || path.indexOf(_file) != -1)) {
                                                files.push({
                                                    path: path,
                                                    status: status
                                                });
                                            }
                                        }
                                    }
                                    if (files.length > 0) {
                                        _commits.push({
                                            sha: _commit.sha(),
                                            email: _commit.author().email(),
                                            date: _commit.date(),
                                            files: files,
                                            message: _commit.message()
                                        });
                                    }
                                    return _walkHistory();
                                }).catch(function(e) {
                                    return e;
                                });
                            } else {
                                return _walkHistory();
                            }
                        });
                    });
                }
                return _walkHistory();
            });
        });
    },
    walkHistoryFile: function(file, size, sha) {
        return git.Repository.open(gitRepository).then(function(repo) {
            var _size = size || 500;
            var _shaCommit = (sha ? repo.getCommit(sha) : repo.getMasterCommit());
            return _shaCommit.then(function(commit) {
                var _commits = [];
                var _file = gitPath(file);
                // walk commit history
                var _walker = repo.createRevWalk();
                var _ending = false;
                _walker.push(commit.sha());
                _walker.sorting(git.Revwalk.SORT.TIME);
                function _walkHistoryFile() {
                    if (_commits.length >= _size || _ending) {
                        return { commits: _commits, ending: _ending };
                    }
                    return _walker.next().then(function(oid) {
                        return repo.getCommit(oid).then(function(_commit) {
                            _ending = _commit.parentcount() == 0;
                            if (_commit.sha() != sha) {
                                return _commit.getDiff().then(function(diffs) {
                                    for (var i = 0; i < diffs.length; i++) {
                                        for (var j = 0; j < diffs[i].numDeltas(); j++) {
                                            var delta = diffs[i].getDelta(j);
                                            if (hasHistoryFile(_file, delta)) {
                                                _commits.push({
                                                    sha: _commit.sha(),
                                                    email: _commit.author().email(),
                                                    date: _commit.date(),
                                                    status: delta.status(),
                                                    message: _commit.message()
                                                });
                                                return _walkHistoryFile();
                                            }
                                        }
                                    }
                                    return _walkHistoryFile();
                                }).catch(function(e) {
                                    return e;
                                });
                            } else {
                                return _walkHistoryFile();
                            }
                        });
                    });
                }
                return _walkHistoryFile();
            });
        });
    }
};
